﻿using System;
using System.Buffers.Binary;
using System.Collections.Generic;
using System.IO.Ports;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using System.Transactions;

namespace SmartPowerHub.Communication.Modbus
{
    public class ModbusTcpClient : ModbusBase, IModbus
    {
        public event DisconnectEvent DisconnectEvent;
        public event ConnectEvent ConnectEvent;
        private ushort _transactionId;
        private Socket _socket;
        private readonly string _ipAddress;
        private readonly int _port;

        public bool IsConnected => _socket == null ? false : _socket.Connected;

        private ModbusTcpHelper modbusTcpHelper;

        public ModbusTcpClient(string ipAddress, int port = 502, EndianFormat endianFormat = EndianFormat.ABCD, StringFormat stringFormat = StringFormat.UTF8)
        {
            _ipAddress = ipAddress;
            _port = port;
            ModbusType = ModbusType.TCP;
            modbusTcpHelper = new ModbusTcpHelper(endianFormat, stringFormat);
        }

        public override async Task ConnectAsync()
        {
            try
            {
                await _sendLock.WaitAsync(_cancellationTokenSource.Token);
                if (_socket == null)
                    _socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
                if (!_socket.Connected)
                {
                    await _socket.ConnectAsync(System.Net.IPAddress.Parse(_ipAddress), _port);
                    ConnectEvent?.Invoke(this);
                }
            }
            catch (Exception ex)
            {
                throw ex;
            }
            finally
            {
                _sendLock.Release();
            }
        }

        public override async Task DisconnectAsync()
        {
            try
            {
                await _sendLock.WaitAsync(_cancellationTokenSource.Token);
                if (_socket == null)
                    return;
                if (_socket.Connected)
                {
                    _socket.Disconnect(true);
                    _socket?.Dispose();
                    DisconnectEvent?.Invoke(this);
                    _socket = null;
                }
            }
            catch (Exception)
            {

            }
            finally
            {
                _sendLock.Release();
            }
        }

        /// <summary>
        /// 读取数据
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="functionCode"></param>
        /// <param name="address"></param>
        /// <param name="count"></param>
        /// <returns></returns>
        protected override async Task<ModbusResponse<T>> SendReadRequestWithReceiveAsync<T>(FunctionCode functionCode, ushort address, ushort count, byte slaveAddressOrUnitId = 0x01)
        {
            if (!_socket.Connected)
            {
                await ConnectAsync();
            }
            try
            {
                await _sendLock.WaitAsync(_cancellationTokenSource.Token);
                _transactionId = modbusTcpHelper.SetNextTransactionId();
                // 构建请求帧
                byte[] request = modbusTcpHelper.BuildReadRequest(functionCode, address, count, _transactionId, slaveAddressOrUnitId);
                //发送请求
                await _socket.SendAsync(request);
                await Task.Delay(50);
                //读取数据
                byte[] buffer = new byte[1024];
                _socket.Receive(buffer, 0, buffer.Length, SocketFlags.None);
                //解析数据
                return await modbusTcpHelper.ReadResponse<T>(buffer, functionCode, count, _transactionId);

            }
            catch (Exception ex)
            {
                return new ModbusResponse<T>(false, message: ex.Message);
            }
            finally
            {
                _sendLock.Release();
            }
        }

        /// <summary>
        /// 写入数据
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="functionCode"></param>
        /// <param name="address"></param>
        /// <param name="values"></param>
        /// <returns></returns>
        protected override async Task<ModbusResponse> SendWriteRequestWithReceiveAsync<T>(FunctionCode functionCode, ushort address, T[] values, byte slaveAddressOrUnitId = 0x01)
        {
            if (!_socket.Connected)
            {
                await ConnectAsync();
            }
            try
            {
                await _sendLock.WaitAsync(_cancellationTokenSource.Token);
                _transactionId = modbusTcpHelper.SetNextTransactionId();
                List<byte> data = new List<byte>();
                foreach (T value in values)
                {
                    data.AddRange(ByteHelper.GetBytes<T>(value));
                }
                byte[] request = modbusTcpHelper.BuildWriteRequest<T>(functionCode, address, data.ToArray(), _transactionId, slaveAddressOrUnitId);
                await _socket.SendAsync(request);
                await Task.Delay(50);
                byte[] buffer = new byte[12];
                _socket.Receive(buffer, 0, buffer.Length, SocketFlags.None);
                return await modbusTcpHelper.ValidateWriteResponse(buffer, functionCode, address);
            }
            catch (SocketException ex)
            {
                string message = string.Empty;
                if (ex.SocketErrorCode == SocketError.TimedOut)
                {
                    message = "连接超时";
                }
                else
                {
                    message = ex.Message;
                }
                return new ModbusResponse(false, message: message);
            }
            catch (Exception ex)
            {
                return new ModbusResponse(false, message: ex.Message);
            }
            finally
            {
                _sendLock.Release();
            }
        }
        #region 写入

        public async Task<ModbusResponse> WriteMulti<T>(ushort address, T[] values, byte slaveAddress = 1, FunctionCode functionCode = FunctionCode.WriteMultipleRegisters)
        {
            var result = await SendWriteRequestWithReceiveAsync<T>(functionCode, address, values);
            return result;
        }

        public async Task<ModbusResponse> WriteUInit16(ushort address, ushort value, byte slaveAddress = 1, FunctionCode functionCode = FunctionCode.WriteSingleRegister)
        {
            List<ushort> values = new List<ushort>() { value };
            var result = await SendWriteRequestWithReceiveAsync<ushort>(functionCode, address, values.ToArray());
            return result;
        }

        public async Task<ModbusResponse> WriteUInit32(ushort address, uint value, byte slaveAddress = 1, FunctionCode functionCode = FunctionCode.WriteMultipleRegisters)
        {
            List<uint> values = new List<uint>() { value };
            var result = await SendWriteRequestWithReceiveAsync<uint>(functionCode, address, values.ToArray());
            return result;
        }

        public async Task<ModbusResponse> WriteS16(ushort address, short value, byte slaveAddress = 1, FunctionCode functionCode = FunctionCode.WriteSingleRegister)
        {
            List<short> values = new List<short>() { value };
            var result = await SendWriteRequestWithReceiveAsync<short>(functionCode, address, values.ToArray());
            return result;
        }

        public async Task<ModbusResponse> WriteS32(ushort address, int value, byte slaveAddress = 1, FunctionCode functionCode = FunctionCode.WriteMultipleRegisters)
        {
            List<int> values = new List<int>() { value };
            var result = await SendWriteRequestWithReceiveAsync<int>(functionCode, address, values.ToArray());
            return result;
        }

        public async Task<ModbusResponse> WriteDouble(ushort address, double value, byte slaveAddress = 1, FunctionCode functionCode = FunctionCode.WriteMultipleRegisters)
        {
            List<double> values = new List<double>() { value };
            var result = await SendWriteRequestWithReceiveAsync<double>(functionCode, address, values.ToArray());
            return result;
        }

        public async Task<ModbusResponse> WriteFloat(ushort address, float value, byte slaveAddress = 1, FunctionCode functionCode = FunctionCode.WriteMultipleRegisters)
        {
            List<float> values = new List<float>() { value };
            var result = await SendWriteRequestWithReceiveAsync<float>(functionCode, address, values.ToArray());
            return result;
        }

        public async Task<ModbusResponse> WriteString(ushort address, string value, byte slaveAddress = 1, FunctionCode functionCode = FunctionCode.WriteMultipleRegisters)
        {
            List<string> values = new List<string>() { value };
            var result = await SendWriteRequestWithReceiveAsync<string>(functionCode, address, values.ToArray());
            return result;
        }

        #endregion

        #region 读取
        public async Task<ModbusResponse<T>> Read<T>(FunctionCode functionCode, ushort startAddress, ushort count, byte slaveAddress)
        {
            return await SendReadRequestWithReceiveAsync<T>(functionCode, startAddress, count);
        }

        public async Task<ModbusResponse<ushort>> ReadUInit16(FunctionCode functionCode, ushort startAddress, ushort count = 1, byte slaveAddress = 1)
        {
            return await SendReadRequestWithReceiveAsync<ushort>(functionCode, startAddress, count);
        }

        public async Task<ModbusResponse<uint>> ReadUInt32(FunctionCode functionCode, ushort startAddress, ushort count = 2, byte slaveAddress = 1)
        {
            return await SendReadRequestWithReceiveAsync<uint>(functionCode, startAddress, count);
        }

        public async Task<ModbusResponse<short>> ReadS16(FunctionCode functionCode, ushort startAddress, ushort count = 1, byte slaveAddress = 1)
        {
            return await SendReadRequestWithReceiveAsync<short>(functionCode, startAddress, count);
        }

        public async Task<ModbusResponse<int>> ReadS32(FunctionCode functionCode, ushort startAddress, ushort count = 2, byte slaveAddress = 1)
        {
            return await SendReadRequestWithReceiveAsync<int>(functionCode, startAddress, count);
        }

        public async Task<ModbusResponse<double>> ReadDouble(FunctionCode functionCode, ushort startAddress, ushort count = 4, byte slaveAddress = 1)
        {
            return await SendReadRequestWithReceiveAsync<double>(functionCode, startAddress, count);
        }

        public async Task<ModbusResponse<float>> ReadFloat(FunctionCode functionCode, ushort startAddress, ushort count = 2, byte slaveAddress = 1)
        {
            return await SendReadRequestWithReceiveAsync<float>(functionCode, startAddress, count);
        }

        public async Task<ModbusResponse<string>> ReadString(FunctionCode functionCode, ushort startAddress, ushort count, byte slaveAddress = 1)
        {
            return await SendReadRequestWithReceiveAsync<string>(functionCode, startAddress, count);
        }

        #endregion
    }
}
